#pragma once
#ifndef MATERIAL_H
#define MATERIAL_H

#include "device/Vector_dev.hpp"
#include "device/dev_utils.hpp"
#include "UnifiedMemManaged.hpp"

enum MaterialType
{
    DIFFUSE
};

class Material : public UnifiedMemManaged
{
private:
    // Compute reflection direction
    __device__ Vector3fDev reflect(const Vector3fDev &I, const Vector3fDev &N) const
    {
        return I - N * 2 * dotProduct(I, N);
    }

    // Compute refraction direction using Snell's law
    //
    // We need to handle with care the two possible situations:
    //
    //    - When the ray is inside the object
    //
    //    - When the ray is outside.
    //
    // If the ray is outside, you need to make cosi positive cosi = -N.I
    //
    // If the ray is inside, you need to invert the refractive indices and negate the normal N
    __device__ Vector3fDev refract(const Vector3fDev &I, const Vector3fDev &N, const float &ior) const
    {
        float cosi = dev_clamp(-1, 1, dotProduct(I, N));
        float etai = 1, etat = ior;
        Vector3fDev n = N;
        if (cosi < 0)
        {
            cosi = -cosi;
        }
        else
        {
            std::swap(etai, etat);
            n = -N;
        }
        float eta = etai / etat;
        float k = 1 - eta * eta * (1 - cosi * cosi);

        return k < 0 ? Vector3fDev(0.F, 0.F, 0.F) : (I * eta + n * (eta * cosi - sqrtf(k)));
    }

    // Compute Fresnel equation
    //
    // \param I is the incident view direction
    //
    // \param N is the normal at the intersection point
    //
    // \param ior is the material refractive index
    //
    // \param[out] kr is the amount of light reflected
    __device__ void fresnel(const Vector3fDev &I, const Vector3fDev &N, const float &ior, float &kr) const
    {
        float cosi = dev_clamp(-1, 1, dotProduct(I, N));
        float etai = 1, etat = ior;
        if (cosi > 0)
        {
            std::swap(etai, etat);
        }
        // Compute sini using Snell's law
        float sint;
        if (0.f > 1 - cosi * cosi)
            sint = 0.f;
        else
            sint = etai / etat * sqrtf(1 - cosi * cosi);

        // Total internal reflection
        if (sint >= 1)
        {
            kr = 1;
        }
        else
        {
            float cost;
            if (0.f > 1 - sint * sint)
                cost = 0.f;
            else
                cost = sqrtf(1 - sint * sint);
            cosi = fabsf(cosi);
            float Rs = ((etat * cosi) - (etai * cost)) / ((etat * cosi) + (etai * cost));
            float Rp = ((etai * cosi) - (etat * cost)) / ((etai * cosi) + (etat * cost));
            kr = (Rs * Rs + Rp * Rp) / 2;
        }
        // As a consequence of the conservation of energy, transmittance is given by:
        // kt = 1 - kr;
    }

    __device__ Vector3fDev toWorld(const Vector3fDev &a, const Vector3fDev &N)
    {
        Vector3fDev B, C;
        if (std::fabs(N.x) > std::fabs(N.y))
        {
            float invLen = 1.0f / std::sqrt(N.x * N.x + N.z * N.z);
            C = Vector3fDev(N.z * invLen, 0.0f, -N.x * invLen);
        }
        else
        {
            float invLen = 1.0f / std::sqrt(N.y * N.y + N.z * N.z);
            C = Vector3fDev(0.0f, N.z * invLen, -N.y * invLen);
        }
        B = crossProduct(C, N);
        return B * a.x + C * a.y + N * a.z;
    }

public:
    MaterialType m_type;
    // Vector3f m_color;
    Vector3fDev m_emission;
    float ior;
    Vector3fDev Kd, Ks;
    float specularExponent;
    // Texture tex;

    __device__ Material(MaterialType t = DIFFUSE, Vector3fDev e = Vector3fDev(0, 0, 0)) : m_type(t), m_emission(e) {}
    __device__ MaterialType getType() { return m_type; };
    // inline Vector3f getColor();
    __device__ Vector3fDev getColorAt(double u, double v)
    {
        return Vector3fDev();
    }

    __device__ Vector3fDev getEmission() { return m_emission; }

    __device__ bool hasEmission()
    {
        if (m_emission.norm() > 0.00001)
            return true;
        else
            return false;
    }

    // sample a ray by Material properties
    __device__ Vector3fDev sample(const Vector3fDev &wi, const Vector3fDev &N, int thread_id)
    {
        switch (m_type)
        {
        case DIFFUSE:
        {
            // uniform sample on the hemisphere
            float x_1 = get_random_float(thread_id), x_2 = get_random_float(thread_id * 2);
            float z = std::fabs(1.0f - 2.0f * x_1);
            float r = std::sqrt(1.0f - z * z), phi = 2 * M_PI * x_2;
            Vector3fDev localRay(r * std::cos(phi), r * std::sin(phi), z);
            return toWorld(localRay, N);

            break;
        }
        }
    }
    // given a ray, calculate the PdF of this ray
    __device__ float pdf(const Vector3fDev &wi, const Vector3fDev &wo, const Vector3fDev &N)
    {
        switch (m_type)
        {
        case DIFFUSE:
        {
            // uniform sample probability 1 / (2 * PI)
            if (dotProduct(wo, N) > 0.0f)
                return 0.5f / M_PI;
            else
                return 0.0f;
            break;
        }
        }
    }

    // given a ray, calculate the contribution of this ray
    __device__ Vector3fDev eval(const Vector3fDev &wi, const Vector3fDev &wo, const Vector3fDev &N)
    {
        switch (m_type)
        {
        case DIFFUSE:
        {
            // calculate the contribution of diffuse   model
            float cosalpha = dotProduct(N, wo);
            if (cosalpha > 0.0f)
            {
                Vector3fDev diffuse = Kd / M_PI;
                return diffuse;
            }
            else
                return Vector3fDev(0.0f, 0.0f, 0.0f);
            break;
        }
        }
    }
};

#endif // RAYTRACING_MATERIAL_H
